package br.univali.compiladores.maquinavirtual;

import java.security.InvalidParameterException;
import java.util.LinkedList;
import java.util.List;

public class MaquinaVirtual {

	private LinkedList<Object> pilha = new LinkedList<Object>();
	public Integer PONTEIRO = 1;
	public In in;
	public Out out;
	public Finish finish;
	
	public MaquinaVirtual(){

	}
	
	
	public List<Object> getPilha () { return pilha; }
	
	public void WRT(Integer zero) {
		out.println(pilha.removeLast());
		PONTEIRO++;
	}
	
	public void SUB(Integer zero) {
		Object valorTopo = pilha.removeLast();
		Object valorSubTopo = pilha.removeLast();
		Integer valorInteger1 = null;
		Float valorFloat1 = null;
		Integer valorInteger2 = null;
		Float valorFloat2 = null;
		
		if (valorSubTopo instanceof Integer) {
			valorInteger1 = (Integer) valorSubTopo; 
		} else if (valorSubTopo instanceof Float) {
			valorFloat1 = (Float) valorSubTopo;
		}
		
		if (valorTopo instanceof Integer) {
			valorInteger2 = (Integer) valorTopo;
		} else if (valorTopo instanceof Float) {
			valorFloat2 = (Float) valorTopo;
		}
		
		if (valorInteger1 != null && valorInteger2 != null) {
			pilha.add( valorInteger1 - valorInteger2);
		} else if (valorInteger1 != null && valorFloat2 != null) {
			pilha.add( valorInteger1 - valorFloat2);
		} else if (valorFloat1 != null && valorInteger2 != null) {
			pilha.add( valorFloat1 - valorInteger2);
		} else if (valorFloat1 != null && valorFloat2 != null) {
			pilha.add( valorFloat1 - valorFloat2);
		} else {
			throw new InvalidParameterException("Parametro errado");
		}
		
		PONTEIRO++;
	}
	
	public void STR(Integer endereco) {
		pilha.set(endereco-1, pilha.removeLast());
		PONTEIRO++;
	}
	
	public void STP(Integer zero) {
		finish.exit(0);
	}
	
	public void SMR(Integer zero) {
		Object valorTopo = pilha.removeLast();
		Object valorSubTopo = pilha.removeLast();
		Integer valorInteger1 = null;
		Float valorFloat1 = null;
		Integer valorInteger2 = null;
		Float valorFloat2 = null;
		
		if (valorSubTopo instanceof Integer) {
			valorInteger1 = (Integer) valorSubTopo; 
		} else if (valorSubTopo instanceof Float) {
			valorFloat1 = (Float) valorSubTopo;
		}
		
		if (valorTopo instanceof Integer) {
			valorInteger2 = (Integer) valorTopo;
		} else if (valorTopo instanceof Float) {
			valorFloat2 = (Float) valorTopo;
		}
		
		if (valorInteger1 != null && valorInteger2 != null) {
			pilha.add( valorInteger1 < valorInteger2);
		} else if (valorInteger1 != null && valorFloat2 != null) {
			pilha.add( valorInteger1 < valorFloat2);
		} else if (valorFloat1 != null && valorInteger2 != null) {
			pilha.add( valorFloat1 < valorInteger2);
		} else if (valorFloat1 != null && valorFloat2 != null) {
			pilha.add( valorFloat1 < valorFloat2);
		} else {
			throw new InvalidParameterException("Parametro errado");
		}
		
		PONTEIRO++;
	}
	
	public void SME(Integer zero) {
		Object valorTopo = pilha.removeLast();
		Object valorSubTopo = pilha.removeLast();
		Integer valorInteger1 = null;
		Float valorFloat1 = null;
		Integer valorInteger2 = null;
		Float valorFloat2 = null;
		
		if (valorSubTopo instanceof Integer) {
			valorInteger1 = (Integer) valorSubTopo; 
		} else if (valorSubTopo instanceof Float) {
			valorFloat1 = (Float) valorSubTopo;
		}
		
		if (valorTopo instanceof Integer) {
			valorInteger2 = (Integer) valorTopo;
		} else if (valorTopo instanceof Float) {
			valorFloat2 = (Float) valorTopo;
		}
		
		if (valorInteger1 != null && valorInteger2 != null) {
			pilha.add( valorInteger1 <= valorInteger2);
		} else if (valorInteger1 != null && valorFloat2 != null) {
			pilha.add( valorInteger1 <= valorFloat2);
		} else if (valorFloat1 != null && valorInteger2 != null) {
			pilha.add( valorFloat1 <= valorInteger2);
		} else if (valorFloat1 != null && valorFloat2 != null) {
			pilha.add( valorFloat1 <= valorFloat2);
		} else {
			throw new InvalidParameterException("Parametro errado");
		}
		PONTEIRO++;
	}
	
	public void REA(String tipo) {		
		String valor = in.readString();
		PONTEIRO++;
		tentaFixarValor(valor, tipo);
	}
	
	public void OR(Integer zero) {
		Object topo = pilha.removeLast();
		Object subTopo = pilha.removeLast();
		if(!(topo instanceof Boolean) ||!(subTopo instanceof Boolean))
			finish.exit(1);
		pilha.add(((Boolean)topo)||((Boolean)subTopo));
		
		PONTEIRO++;
	}
	
	public void NOT(Integer zero) {

		Object topo = pilha.removeLast();
		if(!(topo instanceof Boolean))
			finish.exit(1);
		pilha.add(!((Boolean)topo) );
		PONTEIRO++;
	}
	
	public void MUL(Integer zero) {
		Object valorTopo = pilha.removeLast();
		Object valorSubTopo = pilha.removeLast();
		Integer valorInteger1 = null;
		Float valorFloat1 = null;
		Integer valorInteger2 = null;
		Float valorFloat2 = null;
		
		if (valorSubTopo instanceof Integer) {
			valorInteger1 = (Integer) valorSubTopo; 
		} else if (valorSubTopo instanceof Float) {
			valorFloat1 = (Float) valorSubTopo;
		}
		
		if (valorTopo instanceof Integer) {
			valorInteger2 = (Integer) valorTopo;
		} else if (valorTopo instanceof Float) {
			valorFloat2 = (Float) valorTopo;
		}
		
		if (valorInteger1 != null && valorInteger2 != null) {
			pilha.add( valorInteger1 * valorInteger2);
		} else if (valorInteger1 != null && valorFloat2 != null) {
			pilha.add( valorInteger1 * valorFloat2);
		} else if (valorFloat1 != null && valorInteger2 != null) {
			pilha.add( valorFloat1 * valorInteger2);
		} else if (valorFloat1 != null && valorFloat2 != null) {
			pilha.add( valorFloat1 * valorFloat2);
		} else {
			throw new InvalidParameterException("Parametro errado");
		}
		
		PONTEIRO++;
	}
	
	public void LDS(Object constante) {
		if (!(constante instanceof String)) throw new RuntimeException("Tentativa de carregamento de tipo de vari�vel na pilha inv�lido!");
		pilha.add(constante);
		PONTEIRO++;
	}
	
	public void LDR(Object constante) {
		
		if (!(constante instanceof Float)) throw new RuntimeException("Tentativa de carregamento de tipo de vari�vel na pilha inv�lido!");
		pilha.add(constante);
		PONTEIRO++;
	}
	
	public void LDI(Object constante) {
		if (!(constante instanceof Integer)) throw new RuntimeException("Tentativa de carregamento de tipo de vari�vel na pilha inv�lido!");
		pilha.add(constante);
		PONTEIRO++;
	}

	public void LDB(Object constante) {
		
		if (!(constante instanceof Boolean)) throw new RuntimeException("Tentativa de carregamento de tipo de vari�vel na pilha inv�lido!");
		pilha.add(constante);
		PONTEIRO++;
	}
	
	public void LDV(Integer endereco) {
		pilha.add(pilha.get(endereco-1));
		PONTEIRO++;
	}
	
	public void JMT(Integer endereco) {
		if (((Boolean) pilha.removeLast()) == true) {
			PONTEIRO = endereco;
		} else {
			PONTEIRO++;
		}
		
	}
	
	public void JMP(Integer endereco) {
		PONTEIRO = endereco;
	}
	
	public void ADD(Integer zero) {
		Object valorTopo = pilha.removeLast();
		Object valorSubTopo = pilha.removeLast();
		Integer valorInteger1 = null;
		Float valorFloat1 = null;
		Integer valorInteger2 = null;
		Float valorFloat2 = null;
		
		if (valorSubTopo instanceof Integer) {
			valorInteger1 = (Integer) valorSubTopo; 
		} else if (valorSubTopo instanceof Float) {
			valorFloat1 = (Float) valorSubTopo;
		}
		
		if (valorTopo instanceof Integer) {
			valorInteger2 = (Integer) valorTopo;
		} else if (valorTopo instanceof Float) {
			valorFloat2 = (Float) valorTopo;
		}
		
		if (valorInteger1 != null && valorInteger2 != null) {
			pilha.add( valorInteger1 + valorInteger2);
		} else if (valorInteger1 != null && valorFloat2 != null) {
			pilha.add( valorInteger1 + valorFloat2);
		} else if (valorFloat1 != null && valorInteger2 != null) {
			pilha.add( valorFloat1 + valorInteger2);
		} else if (valorFloat1 != null && valorFloat2 != null) {
			pilha.add( valorFloat1 + valorFloat2);
		} else {
			throw new InvalidParameterException("Parametro errado");
		}
		
		PONTEIRO++;
	}

	public void ALB(Integer deslocamento) {
		alocacaoDeMemoriaNaPilha(deslocamento, false);
	}
	
	public void ALI(Integer deslocamento) {
		alocacaoDeMemoriaNaPilha(deslocamento, 0);
	}

	public void ALR(Integer deslocamento) {
		alocacaoDeMemoriaNaPilha(deslocamento, 0.0);
	}
	
	public void ALS(Integer deslocamento) {
		alocacaoDeMemoriaNaPilha(deslocamento, "");
	}
	
	public void AND(Integer zero) {
		pilha.add(((Boolean) pilha.removeLast()) && (Boolean)pilha.removeLast());
		
		PONTEIRO++;
	}
	
	public void BGE(Integer zero) {
		Object valorTopo = pilha.removeLast();
		Object valorSubTopo = pilha.removeLast();
		Integer valorInteger1 = null;
		Float valorFloat1 = null;
		Integer valorInteger2 = null;
		Float valorFloat2 = null;
		
		if (valorSubTopo instanceof Integer) {
			valorInteger1 = (Integer) valorSubTopo; 
		} else if (valorSubTopo instanceof Float) {
			valorFloat1 = (Float) valorSubTopo;
		}
		
		if (valorTopo instanceof Integer) {
			valorInteger2 = (Integer) valorTopo;
		} else if (valorTopo instanceof Float) {
			valorFloat2 = (Float) valorTopo;
		}
		
		if (valorInteger1 != null && valorInteger2 != null) {
			pilha.add( valorInteger1 >= valorInteger2);
		} else if (valorInteger1 != null && valorFloat2 != null) {
			pilha.add( valorInteger1 >= valorFloat2);
		} else if (valorFloat1 != null && valorInteger2 != null) {
			pilha.add( valorFloat1 >= valorInteger2);
		} else if (valorFloat1 != null && valorFloat2 != null) {
			pilha.add( valorFloat1 >= valorFloat2);
		} else {
			throw new InvalidParameterException("Parametro errado");
		}
		
		PONTEIRO++;
	}
	
	public void BGR(Integer zero) {
		Object valorTopo = pilha.removeLast();
		Object valorSubTopo = pilha.removeLast();
		Integer valorInteger1 = null;
		Float valorFloat1 = null;
		Integer valorInteger2 = null;
		Float valorFloat2 = null;
		
		if (valorSubTopo instanceof Integer) {
			valorInteger1 = (Integer) valorSubTopo; 
		} else if (valorSubTopo instanceof Float) {
			valorFloat1 = (Float) valorSubTopo;
		}
		
		if (valorTopo instanceof Integer) {
			valorInteger2 = (Integer) valorTopo;
		} else if (valorTopo instanceof Float) {
			valorFloat2 = (Float) valorTopo;
		}
		
		if (valorInteger1 != null && valorInteger2 != null) {
			pilha.add( valorInteger1 > valorInteger2);
		} else if (valorInteger1 != null && valorFloat2 != null) {
			pilha.add( valorInteger1 > valorFloat2);
		} else if (valorFloat1 != null && valorInteger2 != null) {
			pilha.add( valorFloat1 > valorInteger2);
		} else if (valorFloat1 != null && valorFloat2 != null) {
			pilha.add( valorFloat1 > valorFloat2);
		} else {
			throw new InvalidParameterException("Parametro errado");
		}
		
		PONTEIRO++;
	}
	
	public void DIF(Integer zero) {
		Object valorTopo = pilha.removeLast();
		Object valorSubTopo = pilha.removeLast();
		Integer valorInteger1 = null;
		Float valorFloat1 = null;
		Integer valorInteger2 = null;
		Float valorFloat2 = null;
		
		if (valorSubTopo instanceof Integer) {
			valorInteger1 = (Integer) valorSubTopo; 
		} else if (valorSubTopo instanceof Float) {
			valorFloat1 = (Float) valorSubTopo;
		}
		
		if (valorTopo instanceof Integer) {
			valorInteger2 = (Integer) valorTopo;
		} else if (valorTopo instanceof Float) {
			valorFloat2 = (Float) valorTopo;
		}
		
		if (valorInteger1 != null && valorInteger2 != null) {
			pilha.add( valorInteger1 != valorInteger2);
		} else if (valorInteger1 != null && valorFloat2 != null) {
			pilha.add( valorInteger1.floatValue() != valorFloat2);
			//TODO VERIFICAR ISSO
		} else if (valorFloat1 != null && valorInteger2 != null) {
			pilha.add( valorFloat1 != valorInteger2.floatValue());
			//TODO VERIFICAR ISSO
		} else if (valorFloat1 != null && valorFloat2 != null) {
			pilha.add( valorFloat1 != valorFloat2);
		} else {
			throw new InvalidParameterException("Parametro errado");
		}
		
		PONTEIRO++;
	}
	
	public void DIV(Integer zero) {
		Object valorTopo = pilha.removeLast();
		if (valorTopo == (Integer) 0) {
			out.println("RUNTIME error: divisao por 0");
			throw new ArithmeticException();
		}
		
		Object valorSubTopo = pilha.removeLast();
		Integer valorInteger1 = null;
		Float valorFloat1 = null;
		Integer valorInteger2 = null;
		Float valorFloat2 = null;
		
		if (valorSubTopo instanceof Integer) {
			valorInteger1 = (Integer) valorSubTopo; 
		} else if (valorSubTopo instanceof Float) {
			valorFloat1 = (Float) valorSubTopo;
		}
		
		if (valorTopo instanceof Integer) {
			valorInteger2 = (Integer) valorTopo;
		} else if (valorTopo instanceof Float) {
			valorFloat2 = (Float) valorTopo;
		}
		
		if (valorInteger1 != null && valorInteger2 != null) {
			pilha.add( valorInteger1 / valorInteger2);
		} else if (valorInteger1 != null && valorFloat2 != null) {
			pilha.add( valorInteger1 / valorFloat2);
		} else if (valorFloat1 != null && valorInteger2 != null) {
			pilha.add( valorFloat1 / valorInteger2);
		} else if (valorFloat1 != null && valorFloat2 != null) {
			pilha.add( valorFloat1 / valorFloat2);
		} else {
			throw new InvalidParameterException("Parametro errado");
		}
		
		PONTEIRO++;
	}
	
	public void EQL(Integer zero) {
		Object valorTopo = pilha.removeLast();
		Object valorSubTopo = pilha.removeLast();
		Integer valorInteger1 = null;
		Float valorFloat1 = null;
		Integer valorInteger2 = null;
		Float valorFloat2 = null;
		
		if (valorSubTopo instanceof Integer) {
			valorInteger1 = (Integer) valorSubTopo; 
		} else if (valorSubTopo instanceof Float) {
			valorFloat1 = (Float) valorSubTopo;
		}
		
		if (valorTopo instanceof Integer) {
			valorInteger2 = (Integer) valorTopo;
		} else if (valorTopo instanceof Float) {
			valorFloat2 = (Float) valorTopo;
		}

		if (valorInteger1 != null && valorInteger2 != null) {
			pilha.add( valorInteger1 == valorInteger2);
		} else if (valorInteger1 != null && valorFloat2 != null) {
			pilha.add( valorInteger1.floatValue() == valorFloat2);
		} else if (valorFloat1 != null && valorInteger2 != null) {
			pilha.add( valorFloat1 == valorInteger2.floatValue());
		} else if (valorFloat1 != null && valorFloat2 != null) {
			pilha.add( valorFloat1 == valorFloat2);
		} else {
			throw new InvalidParameterException("Parametro errado");
		}
		
		PONTEIRO++;
	}
	
	public void JMF(Integer endereco) {
		if (!(Boolean) pilha.removeLast()) {
			PONTEIRO = endereco;
		} else {
			PONTEIRO++;
		}
		
	}
	
	public void STC(Integer deslocamento) {
		int tamanhoNovo = pilha.size() - deslocamento;
		while (pilha.size() >= tamanhoNovo) { //PARA i DE (topo - deslocamento) AT� (topo - 1)
			 pilha.removeLast();
		}
		
		PONTEIRO++;
	}
	
	public void MOD(Integer zero) {
		Object valorTopo = pilha.removeLast();
		
		if (valorTopo == (Integer) 0) {
			out.println("RUNTIME error: divisao por 0");
			throw new ArithmeticException();
		}
		
		
		Object valorSubTopo = pilha.removeLast();
		Integer valorInteger1 = null;
		Float valorFloat1 = null;
		Integer valorInteger2 = null;
		Float valorFloat2 = null;
		
		if (valorSubTopo instanceof Integer) {
			valorInteger1 = (Integer) valorSubTopo; 
		} else if (valorSubTopo instanceof Float) {
			valorFloat1 = (Float) valorSubTopo;
		}
		
		if (valorTopo instanceof Integer) {
			valorInteger2 = (Integer) valorTopo;
		} else if (valorTopo instanceof Float) {
			valorFloat2 = (Float) valorTopo;
		}
		
		if (valorInteger1 != null && valorInteger2 != null) {
			pilha.add( valorInteger1 % valorInteger2);
		} else if (valorInteger1 != null && valorFloat2 != null) {
			pilha.add( valorInteger1 % valorFloat2);
		} else if (valorFloat1 != null && valorInteger2 != null) {
			pilha.add( valorFloat1 % valorInteger2);
		} else if (valorFloat1 != null && valorFloat2 != null) {
			pilha.add( valorFloat1 % valorFloat2);
		} else {
			throw new InvalidParameterException("Parametro errado");
		}
		PONTEIRO++;
	}
	
	public void DVI(Integer zero) {
		Object valorTopo = pilha.removeLast();
		Object valorSubTopo = pilha.removeLast();
		
		Integer valor1 = null;
		Integer valor2 = null;
		
		try {
			valor1 = (Integer) valorSubTopo;
			valor2 = (Integer) valorTopo;
		} catch (ClassCastException e) {
			throw new RuntimeException("INVALID PARAMETER");
		}
		
		pilha.add(valor1 / valor2);
		
		PONTEIRO++;
	}
	
	public void POW(Integer zero) {
		Object valorTopo = pilha.removeLast();
		Object valorSubTopo = pilha.removeLast();
		
		Integer valorInteger1 = null;
		Float valorFloat1 = null;
		Integer valorInteger2 = null;
		Float valorFloat2 = null;
		
		if (valorSubTopo instanceof Integer) {
			valorInteger1 = (Integer) valorSubTopo; 
		} else if (valorSubTopo instanceof Float) {
			valorFloat1 = (Float) valorSubTopo;
		}
		
		if (valorTopo instanceof Integer) {
			valorInteger2 = (Integer) valorTopo;
		} else if (valorTopo instanceof Float) {
			valorFloat2 = (Float) valorTopo;
		}
		
		if (valorInteger1 != null && valorInteger2 != null) {
			pilha.add((int) Math.pow(valorInteger1, valorInteger2));
		} else if (valorInteger1 != null && valorFloat2 != null) {
			pilha.add((float) Math.pow(valorInteger1, valorFloat2));
		} else if (valorFloat1 != null && valorInteger2 != null) {
			pilha.add( (float) Math.pow(valorFloat1, valorInteger2));
		} else if (valorFloat1 != null && valorFloat2 != null) {
			pilha.add( (float) Math.pow(valorFloat1, valorFloat2));
		} else {
			throw new InvalidParameterException("Parametro errado");
		}
		
		PONTEIRO++;
	}
	
	private void alocacaoDeMemoriaNaPilha(Integer deslocamento, Object valor) {
		for (Integer i = 0; i < deslocamento; ++i) {
			pilha.add(valor);
		}
		
		PONTEIRO++;
	}
	
	private void tentaFixarValor(String valor, String tipo) {
		if (tipo.equals("1")) {
			pilha.add( Integer.parseInt(valor)); 
		} else if (tipo.equals("2")) {
			pilha.add( Float.parseFloat(valor));
		} else if (tipo.equals("4")) {
			pilha.add( Boolean.parseBoolean(valor));
		} else if (tipo.equals("3")) {
			pilha.add( valor);
		} else {

			out.println("RUNTIME error");
			finish.exit(1);
		}
	}
	
	@Override
	public String toString() {
		String pilhaString = "Tabela:";
		
		for (Object item : pilha) {
			pilhaString += "\n";
			pilhaString += item;
		}
		
		return pilhaString;
	}
}